package com.application.examples.utils;


import com.application.examples.entity.BaseException;
import com.application.examples.entity.CobraResultCodeConstant;
import com.application.examples.entity.HttpResponseBody;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author :
 * @date : 2019/10/14 10:42
 */
public final class RestTemplateUtils {
    private static final Logger log = LoggerFactory.getLogger(RestTemplateUtils.class);
    private static final int CONNECT_TIME_OUT = 20*1000; //连接超时时长，单位毫秒
    private static final int READ_TIME_OUT = 2*60*1000;  //读取数据超时时长，单位毫秒

    private static RestTemplate restTemplate;

    static {
        initRestTemplate();
    }

    /**
     * 初始化restTemplate
     */
    private static void initRestTemplate(){
        if (restTemplate == null){
            SSLContextBuilder builder = new SSLContextBuilder();
            SSLConnectionSocketFactory socketFactory = null;
            try {
                //全部信任 不做身份鉴定
                builder.loadTrustMaterial(null, (X509Certificate[] x509Certificate, String s) -> true);
                //客户端支持SSLv2Hello，SSLv3,TLSv1，TLSv1
                socketFactory = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);

                //为自定义连接器注册http与https
                Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", socketFactory).build();
                PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
                phccm.setMaxTotal(500);

                HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
                HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
                factory.setConnectTimeout(CONNECT_TIME_OUT);
                factory.setReadTimeout(READ_TIME_OUT);
                restTemplate = new RestTemplate();
                restTemplate.setRequestFactory(factory);
            }catch (Exception e){
                log.error("RestTemplate init has error",e);
                throw new BaseException(CobraResultCodeConstant.INTERNAL_ERROR,e);
            }
        }
    }

    /**
     *  http 请求 GET
     * @param url 地址
     * @param params 参数
     * @return String 类型
     */
    public static HttpResponseBody doGet(String url, HashMap params , Map headersMap) {
        try {
            // 设置编码集
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            // 异常处理
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
            // 设置·header信息
            HttpEntity requestEntity = null;
            HttpHeaders requestHeaders = null;
            if(headersMap != null){
                requestHeaders = new HttpHeaders();
                requestHeaders.setAll(headersMap);
                requestEntity =  new HttpEntity<>(requestHeaders);
            }
            if(params != null && params.size() > 0){
                url = expandURL(url, params);
            }

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("doGet url:{}, param:{}, headersMap:{} error",url,params,headersMap,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }
    }

    /**
     * get https请求并传递认证
     * @param url 请求地址
     * @param params 请求参数
     * @param headersMap 请求头
     * @param username 认证用户
     * @param password 认证密码
     * @return
     */
    private static HttpResponseBody doGetByCredentials(String url, HashMap params, Map headersMap, String username, String password) {
        try {
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
            restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor(username, password));
            // 设置·header信息
            HttpEntity requestEntity = null;
            HttpHeaders requestHeaders = null;
            if(headersMap != null){
                requestHeaders = new HttpHeaders();
                requestHeaders.setAll(headersMap);
                requestEntity =  new HttpEntity<>(requestHeaders);
            }
            if(params != null && params.size() > 0){
                url = expandURL(url, params);
            }
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("doGetByCredentials url:{}, params:{}, headersMap:{}, username:{}, password:{} error",url,params,headersMap,username,password,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }
    }

    /**
     *  http 请求 post
     * @param url 地址
     * @param params 参数
     * @param headersMap header
     * @return String 类型
     */
    public static <T> HttpResponseBody doPost(String url, T params, Map headersMap) {
        try {
            //内部实际实现为 HttpClient
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
            // 设置 requestEntity信息
            HttpEntity requestEntity = convertRequestEntity(params,headersMap);
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("doPost url:{}, params:{}, headersMap:{} error",url,params,headersMap,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }
    }

    /**
     * post https请求，并传递账号密码认证
     * @param url 请求地址
     * @param params    参数
     * @param headersMap    请求头
     * @param username  认证账号
     * @param password  认证密码
     * @param <T>
     * @return
     */
    private static <T> HttpResponseBody doPostByCredentials(String url, T params, Map headersMap, String username, String password) {
        try {
            restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor(username, password));
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

            // 设置 requestEntity信息
            HttpEntity requestEntity = convertRequestEntity(params,headersMap);
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("doPostByCredentials url:{}, params:{}, headersMap:{}, username:{}, password:{} error",url,params,headersMap,username,password,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }

    }

    /**
     *  http 请求 delete
     * @param url 地址
     * @param params 参数
     * @param headersMap header 请求头
     * @return String 类型
     */
    public static <T> HttpResponseBody deleteHttp(String url, T params, Map headersMap) {
        try {
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
            // 设置 requestEntity信息
            HttpEntity requestEntity = convertRequestEntity(params,headersMap);

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("deleteHttp url:{}, params:{}, headersMap:{}  error",url,params,headersMap,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }
    }

    /**
     * MULTIPART_FORM_DATA 上传本地文件及参数
     * @param url 请求url
     * @param localFilePath 本地文件路径
     * @param params    附属参数
     * @param headersMap    请求头
     * @param <T>
     * @return
     */
    public static <T> HttpResponseBody uploadLocalFile(String url, String localFilePath, HashMap params, Map headersMap) {
        try {
            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
            restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
            // 设置·header信息
            MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
            parts.add("file", new FileSystemResource(new File(localFilePath)));
            HttpEntity requestEntity = null;
            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
            if(headersMap != null){
                requestHeaders.setAll(headersMap);
            }

            if(params != null && params.size() > 0){
                params.forEach((K, V) -> {
                    parts.add((String)K,V);
                });
            }
            if(params != null && headersMap != null){
                requestEntity = new HttpEntity<>(parts, requestHeaders);
            }else if(params != null && headersMap == null){
                requestEntity = new HttpEntity<>(parts);
            }

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity,String.class);
            return new HttpResponseBody(response.getStatusCodeValue(), response.getBody());
        } catch (BaseException e) {
            log.error("uploadLocalFile url:{}, localFilePath:{}, params:{}, headersMap:{}  error",url,localFilePath,params,headersMap,e);
            return new HttpResponseBody(Integer.parseInt(e.getCode()), e.getMsg());
        }
    }

    /**
     * 下载远程文件，转为 byte[]，自行处理
     * @return  byte[]
     * @throws BaseException
     */
    public static byte[] download(String url) throws BaseException {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 异常处理
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        RequestCallback requestCallback = request -> request.getHeaders()
                .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
        ResponseExtractor<File> responseExtractor = clientHttpResponse -> {
            File ret = File.createTempFile("download", "tmp");
            FileOutputStream outputStream = new FileOutputStream(ret);
            StreamUtils.copy(clientHttpResponse.getBody(), outputStream);
            outputStream.close();
            return ret;
        };
        try {
            File file = restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
            byte[] data = ImageUtils.getByteFromFile(file);
            boolean deleteResult = file.delete();
            if(!deleteResult){
                log.error("download function delete tmp {} file error",file.getPath());
            }
            return data;
        }catch (BaseException e){
            log.error("download url:{}  error",url,e);
            return null;
        }
    }

    /**
     * 下载远程文件到本地
     * @param url 远程文件url
     * @param destPath 保存为本地地址
     * @throws BaseException
     */
    public static void download(String url, String destPath) throws BaseException {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        // 异常处理
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        RequestCallback requestCallback = request -> request.getHeaders()
                .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
        // getForObject会将所有返回直接放到内存中,使用流来替代这个操作
        ResponseExtractor<Void> responseExtractor = response -> {
            Files.copy(response.getBody(), Paths.get(destPath));
            return null;
        };
        try {
            restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
        } catch (BaseException e) {
            log.error("download url:{}, destPath:{}  error",url,destPath,e);
        }

    }

    private static String expandURL(String url, HashMap params) {
        StringBuilder sb = new StringBuilder(url);
        sb.append("?");
        params.forEach((K, V) -> {
            sb.append(K).append("=").append(V).append("&");
        });
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    private static <T> HttpEntity convertRequestEntity(T params, Map headersMap){
        // 设置·header信息
        HttpEntity requestEntity = null;
        HttpHeaders requestHeaders = null;
        if(headersMap != null){
            requestHeaders = new HttpHeaders();
            requestHeaders.setAll(headersMap);
        }
        if(params != null && headersMap != null){
            requestEntity = new HttpEntity<>(params, requestHeaders);
        }else if(params != null && headersMap == null){
            requestEntity = new HttpEntity<>(params);
        }else if(params == null && headersMap != null){
            requestEntity = new HttpEntity<>(requestHeaders);
        }
        return requestEntity;
    }


    /**
     * @ClassName: DefaultResponseErrorHandler
     * @Description: TODO
     * @author:
     * @date: 2
     */
    private static class DefaultResponseErrorHandler implements ResponseErrorHandler {
        /** 对response进行判断，如果是异常情况，返回true */
        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
            return response.getStatusCode().value() != HttpServletResponse.SC_OK;
        }

        /** 异常情况时的处理方法 */
        @Override
        public void handleError(ClientHttpResponse response) throws BaseException {
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(response.getBody()));
                StringBuilder sb = new StringBuilder();
                String str = null;
                while ((str = br.readLine()) != null) {
                    sb.append(str);
                }
                throw new BaseException(String.valueOf(response.getRawStatusCode()),sb.toString());
            }catch (IOException e){
                log.error("RestTemplate handleError",e);
                throw new BaseException(CobraResultCodeConstant.INTERNAL_ERROR.getCode(),String.format(CobraResultCodeConstant.INTERNAL_ERROR.getMsg(),e.getLocalizedMessage()));
            }
        }
    }

}
